#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Inversion of Control container inspired by the Castle WindsorContainer."""

namespace Grawlix.Services
import Grawlix.Meta
import C5
import System
import System.Reflection
import System.Collections.Generic as SCG
import System.Security.Permissions

transient class ServiceProvider(PermanentMarshalByRefObject):
"""Creation and loading of service objects by their type."""
	
	def constructor():
	"""Initializes a default ServiceProvider."""
		self(null, null)
	
	def constructor(config as ServiceConfiguration):
	"""
	Initializes a ServiceProvider with specified configuration.
	Param config: The configuration information for this provider.
	"""
		self(config, null)
	
	def constructor(
		config as ServiceConfiguration,
		parent as ServiceProvider):
	"""
	Initializes a ServiceProvider with specified configuration and parent.
	Param config: The configuration information for this provider.
	Param parent: The provider to delegate unhandled requests to.
	"""
		if config is null:
			config = ServiceConfiguration()
		_friendlyName = config.FriendlyName
		composite = CompositeTypeConverter()
		_converter = composite
		for converter in config.Converters:
			type = Type.GetType(converter.Type, false, true)
			if type is null or \
				not typeof(ITypeConverter).IsAssignableFrom(type):
				raise ServiceLoadException(
					"A converter node (${converter.Key}) specifies " +
					"a type (${converter.Type}) that could not be resolved. " +
					"Check to make sure it is spelled correctly and " + 
					"present in the resolving AppDomain.")
			instance as ITypeConverter = type()
			composite.Converters.Add(instance)
		# AddAll broken, see BOO-1076
		for service in config.Services:
			_serviceList.Add(ServiceLoadInfo(service))
		_parent = parent
		
		#ConcurrentBoo.Sync.Cas(_localProvider, null, self)
		_localProvider = self if _localProvider is null
			
		if _localProvider is self:
			#ConcurrentBoo.Sync.Cas(_currentProvider, null, self)
			_currentProvider = self if _currentProvider is null
			
			AppDomain.CurrentDomain.SetData(
				_serviceProviderData,
				self,
				SecurityPermission(
					SecurityPermissionFlag.SerializationFormatter))
		log Info, "${self} constructed"
	
	def FindService[of TService]():
	"""
	Finds a specified type of service.
	Returns: The service found.
	Raises ServiceLoadException: If the service type could not be matched.
	"""
		return FindService(typeof(TService)) as TService
	
	def FindService([required] serviceType as Type):
	"""
	Finds a specified type of service.
	Param serviceType: The type of service to locate.
	Returns: The service found.
	Raises ServiceLoadException: If the service type could not be matched.
	"""
		service = TryFindService(serviceType)
		return service unless service is null
		raise ServiceLoadException(
			"${self} could not find a requested service of " +
			"the specified type (${serviceType}). " +
			"Ensure that the ServiceProvider configuration is " +
			"set up correctly.")
	
	def FindService([required] key as string):
	"""
	Finds a service by its given key.
	Param key: The id key of a specific service implementation.
	Returns: The service found.
	Raises ServiceLoadException: If the key could not be matched.
	"""
		service = TryFindService(key)
		return service unless service is null
		raise ServiceLoadException(
			"${self} could not find a requested service matching " +
			"the specified key (${key}). " +
			"Ensure that the ServiceProvider configuration is " +
			"set up correctly.")
	
	def FindService[of TService]([required] key as string):
	"""
	Find a specified type of service with the specified key.
	Param key: The key to restrict to.
	Returns: The service found.
	Raises ServiceLoadException: If the service type could not be matched.
	"""
		return FindService(typeof(TService), key) as TService
	
	def FindService(
		[required] serviceType as Type,
		[required] key as string):
	"""
	Find a specified type of service with the specified key.
	Param serviceType: The type of service to find.
	Param key: The key to restrict to.
	Returns: The service found.
	Raises ServiceLoadException: If the service type could not be matched.
	"""
		service = TryFindService(serviceType, key)
		return service unless service is null
		raise ServiceLoadException(
			"${self} could not find a requested service of " +
			"the specified type (${serviceType}) with " +
			"the specified key (${key}). " +
			"Ensure that the ServiceProvider configuration is " +
			"set up correctly.")
	
	def TryFindService[of TService]():
	"""
	Attempts to find a specified type of service.
	Returns: The service found or null if not matched.
	"""
		return TryFindService(typeof(TService)) as TService
	
	[lock(_serviceCache)]
	def TryFindService([required] serviceType as Type) as object:
	"""
	Attempts to find a specified type of service.
	Param serviceType: The service type to locate.
	Returns: The service found or null if not matched.
	"""
		if not serviceType.IsInterface:
			raise ArgumentException(
				"The service type specified must be an interface.",
				"serviceType")
		service = null
		if not _typeCache.FindOrAdd(serviceType, service):
			generator = s for s in _serviceList \
				if serviceType.IsAssignableFrom(s.Type)
			service = Resolve(generator)
			if service is null and _parent is not null:
				service = _parent.TryFindService(serviceType)
			_typeCache[serviceType] = service
		return service
	
	[lock(_serviceCache)]
	def TryFindService([required] key as string) as object:
	"""
	Attempts to find a service by its given key.
	Param key: The id key of a specific service implementation.
	Returns: The service found or null if not matched.
	"""
		service = null
		if not _keyCache.FindOrAdd(key, service):
			generator = s for s in _serviceList \
				if s.Key == key
			service = Resolve(generator)
			if service is null and _parent is not null:
				service = _parent.TryFindService(key)
			_keyCache[key] = service
		return service
	
	def TryFindService[of TService]([required] key as string):
	"""
	Attempts to find a service by its given type and key.
	Param key: The id key of a specific service implementation.
	Returns: The service found or null if not matched.
	"""
		return TryFindService(typeof(TService), key) as TService
	
	[lock(_serviceCache)]
	def TryFindService(
		[required] serviceType as Type,
		[required] key as string) as object:
	"""
	Attempts to find a service by its given type and key.
	Param serviceType: The service type to locate.
	Param key: The id key of a specific service implementation.
	Returns: The service found or null if not matched.
	"""
		if not serviceType.IsInterface:
			raise ArgumentException(
				"The service type specified must be an interface.",
				"serviceType")
		pair = ServiceKeyPair(key, serviceType)
		service = null
		if not _bothCache.FindOrAdd(pair, service):
			generator = s for s in _serviceList \
				if serviceType.IsAssignableFrom(s.Type) and s.Key == key
			service = Resolve(generator)
			if service is null and _parent is not null:
				service = _parent.TryFindService(serviceType, key)
			_bothCache[pair] = service
		return service
	
	private def Resolve(services as ServiceLoadInfo*) as object:
	"""
	Resolves the first service of a list that it can match.
	Param services: The list of services to attempt to match.
	Returns: The instantiated service, or null.
	"""
		# Implementation can cause services to be
		# instantiated when it turns out they are not needed.
		for service in services:
			try:
				cached = null
				if _serviceCache.FindOrAdd(service, cached):
					continue if cached is null
					return cached
				constructors = service.Type.GetConstructors()
				Array.Sort(constructors, ConstructorComparer())
				for method in constructors:
					result = ResolveConstructor(service, method)
					if result is not null:
						_serviceCache[service] = result
						_inverseCache[result] = service
						return result
				log Warn, "Unable to load service: ${service}. " + \
					"No constructors could be matched."
			except exception:
			# Note the service that failed
				log Error, "Error during service load: ${service}\n" + \
					exception
		return null
	
	private def ResolveConstructor(
		service as ServiceLoadInfo,
		method as ConstructorInfo):
	"""
	Resolves a constructor if it matches.
	Param service: The service information to match with.
	Param method: The constructor to attempt to match.
	Returns: The instantiated service, or null.
	"""
		params = method.GetParameters()
		args = array(object, params.Length)
		for index in range(params.Length):
			param = params[index]
			name = param.Name
			type = param.ParameterType
			if service.Parameters.Contains(name):
				return null unless _converter.IsSupported(type)
				data = service.Parameters[name]
				return null if data is null
				args[index] = _converter.Convert(self, data, type)
			elif type.IsInterface:
				args[index] = TryFindService(type)
			return null if args[index] is null
		result = method.Invoke(args)
		log Info, "Service found and loaded: ${service.Key} ${result}"
		return result
	
	[lock(_serviceCache)]
	def GetServiceKey([required] service):
	"""
	Figures out the key of a loaded service.
	Param service: The service to look up.
	Returns: The key of the service.
	Raises ArgumentException:
		service is not a service object loaded by this provider.
	"""
		if not _inverseCache.Contains(service):
			raise ArgumentException(
				"The argument was not loaded by this <ServiceProvider>.",
				"service")
		return _inverseCache[service].Key
	
	override def ToString():
	"""
	Gets a string representation for this object.
	Returns: A string representation for this object.
	"""
		return "ServiceProvider ${FriendlyName}"
	
	[getter(TypeConverter)]
	private _converter as ITypeConverter
	"""The type converter used to resolve parameters."""
	
	[getter(FriendlyName)]
	private _friendlyName as string
	"""A friendly name for the provider."""
	
	private _parent as ServiceProvider
	"""The fallback provider in case a service cannot be matched."""
	
	private _typeCache = HashDictionary[of Type, object]()
	"""The cache of loaded services by type."""
	
	private _keyCache = HashDictionary[of string, object]()
	"""The cache of loaded services by key."""
	
	private _bothCache = HashDictionary[of ServiceKeyPair, object]()
	"""The cache of all locally loaded services by key/service pairs."""
	
	private _serviceCache = HashDictionary[of ServiceLoadInfo, object]()
	"""The cache of all locally loaded services by their real type."""
	
	private _inverseCache = HashDictionary[of object, ServiceLoadInfo]()
	"""The inverse of the service cache."""
	
	private _serviceList = ArrayList[of ServiceLoadInfo]()
	"""A prioritized list of all the available services."""

	static CurrentProvider:
	"""
	The currently active ServiceProvider.
	Only for use during serialization to retrieve services.
	"""
		[SecurityPermission(
			SecurityAction.LinkDemand,
			Flags: SecurityPermissionFlag.SerializationFormatter)]
		get:
			if _currentProvider is null:
				parent = \
					AppDomain.CurrentDomain.GetData(_parentAppDomainData) as \
						AppDomain
				current = \
					parent.GetData(_serviceProviderData) as ServiceProvider
				_currentProvider = current unless current is null
				#ConcurrentBoo.Sync.Cas(_currentProvider, null, current)
				if _currentProvider is null:
					raise InvalidOperationException(
						"Unable to access the current service provider in " +
						"AppDomain ${AppDomain.CurrentDomain.FriendlyName}. " +
						"No service provider is yet active.")
			return _currentProvider
	
	internal static LocalProvider:
	"""
	Private access to the local provider.
	"""
		get:
			return _localProvider
	
	private static _serviceProviderData = "SERVICE_PROVIDER"
	"""The string where service provider data is stored."""
	
	private static _parentAppDomainData = "PARENT_APPDOMAIN"
	"""The string where the parent AppDomain is stored."""
	
	private static _currentProvider as ServiceProvider
	"""The currently active ServiceProvider."""
	
	private static _localProvider as ServiceProvider
	"""The currently active ServiceProvider."""
	
internal class ServiceLoadInfo():
"""Service info with loaded types."""
	
	def constructor([required] info as ServiceInfo):
	"""
	Loads the info into this object.
	Param info: The info to resolve.
	"""
		_key = info.Key
		_parameters = info.Parameters.Clone()
		_type = Type.GetType(info.Type, false, true)
		if _type is null:
			raise ServiceLoadException(
				"A converter node (${info.Key}) specifies " +
				"a type (${info.Type}) that could not be resolved. " +
				"Check to make sure it is spelled correctly and " + 
				"present in the resolving AppDomain.")
	
	override def ToString():
	"""
	Gets a string representation for this object.
	Returns: A string representation for this object.
	"""
		return "ServiceLoadInfo ${Key}=${Type}"
	
	[getter(Key)]
	private _key as string
	"""The id key for this particular service."""
	
	[getter(Type)]
	private _type as Type
	"""The type to instantiate."""
	
	Parameters as IDictionary[of string, string]:
	"""Lookup dictionary of parameter name-value pairs."""
		get:
			return GuardedDictionary[of string, string](_parameters)
	
	private _parameters as IDictionary[of string, string]
	"""Lookup dictionary of annotated parameters."""

internal struct ServiceKeyPair():
"""A pair type containing a service type and key."""
	
	def constructor([required] key as string, [required] service as Type):
	"""
	Initializes the pair.
	Param key: The key data.
	Param service: The service type.
	"""
		_key = key
		_service = service
	
	[Equality]
	private def Equality(other as ServiceKeyPair):
	"""
	Do equality testing.
	Param other: The type to test against.
	Returns: true if equal, else false.
	"""
		return _key == other.Key and _service == other.Service
	
	override def GetHashCode():
	"""
	Gets the hash code for this instance.
	Returns: The hash code.
	"""
		return _key.GetHashCode() ^ _service.GetHashCode()
	
	override def ToString():
	"""
	Gets a string representation for this object.
	Returns: A string representation for this object.
	"""
		return "ServiceKeyPair ${Key}=${Type}"
	
	[getter(Key)]
	private _key as string
	"""The id key for this particular service."""
	
	[getter(Service)]
	private _service as Type
	"""The service type."""

internal class ConstructorComparer(SCG.IComparer[of ConstructorInfo]):
"""Compare constructors, preferring longer ones."""
	
	def Compare(lhs as ConstructorInfo, rhs as ConstructorInfo):
	"""Compare two constructors for length."""
		return rhs.GetParameters().Length - lhs.GetParameters().Length
	
